﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WMemoryProfiler.Memory
{
    /// <summary>
    /// Assert when certain memory thresholds are reached.
    /// </summary>
    public class DiffAssert
    {
        IEnumerable<DiffThreshold> Thresholds;
        Action<string, DiffThreshold> OnAssert;

        /// <summary>
        /// Assert when one or more of the given thresholds is reached.
        /// </summary>
        /// <param name="thresholds">collection of memory threshold values</param>
        /// <param name="onAssert">Callback which is called when you want to assert.</param>
        public DiffAssert(IEnumerable<DiffThreshold> thresholds, Action<string,DiffThreshold> onAssert)
        {
            if (thresholds == null)
            {
                throw new ArgumentNullException("thresholds");
            }

            if (onAssert == null)
            {
                throw new ArgumentNullException("onAssert");
            }

            Thresholds = thresholds;
            OnAssert = onAssert;
        }

        /// <summary>
        /// Assert when one or more of the given thresholds is reached.
        /// </summary>
        /// <param name="threshold">One Memory threshold value.</param>
        /// <param name="onAssert">Callback which is called when you want to assert.</param>
        public DiffAssert(DiffThreshold threshold, Action<string,DiffThreshold> onAssert): 
            this(new DiffThreshold[] { CheckForNull(threshold,"threshold") }, onAssert)
        {
        }

        /// <summary>
        /// Check if collection of instance diffs does violate any threshold. If yes then the onAssert method supplied of the 
        /// ctor is called.
        /// </summary>
        /// <param name="diffs">Collection of Instance diffs</param>
        public void Assert(InstanceInfoDiff[] diffs)
        {
            foreach (var threshold in Thresholds)
            {
                threshold.Assert(diffs, this);
            }
        }

        static T CheckForNull<T>(T obj, string argName) where T : class
        {
            if (obj == null)
            {
                throw new ArgumentNullException(argName);
            }
            return obj;
        }

        internal void Assert(DiffThreshold diffThreshold, List<WMemoryProfiler.Memory.DiffThreshold.AssertItem> violations)
        {
            string message = String.Format("Memory threshold {0}for type {1} was violated {2}",  diffThreshold.Name == null ? "" : diffThreshold.Name + " ", 
                diffThreshold.Type == null ? "all" : diffThreshold.Type,
                String.Join(Environment.NewLine, violations.Select(x => String.Format("{0:N0} {1} (limit {2:N0} {3})", x.CurrentValue,
                    GetUnit(x.AssertedProperty),
                    x.MaxValue,
                    x.AssertedProperty)).ToArray()));

            OnAssert(message, diffThreshold);
        }

        string GetUnit(string propertyName)
        {
            string lret = "";
            switch(propertyName)
            {
                case "MaxTotalInstances":
                    lret = "total instances";
                    break;
                case "MaxAddedInstances":
                    lret = "new instances";
                    break;
                case "MaxAddedBytes":
                    lret = "bytes allocated";
                    break;
                case "MaxTotalBytes":
                    lret = "bytes total";
                    break;
                default:
                    throw new NotSupportedException(String.Format("Asserted property {0} was not expected", propertyName));
            }

            return lret;
        }
    }
}
